#include "elf_parser.h"

#include "macro.h"

#include <iostream>

namespace analyzer::posix
{

template <typename entity>
static inline std::vector<entity> read_array(std::ifstream& file, size_t offset, size_t count)
{
    file.seekg(offset);

    std::vector<entity> array(count);
    for (auto& item : array)
        file.read(reinterpret_cast<char*>(&item), sizeof(item));

    return array;
}

template <uint8_t bitness, typename elf_sections = std::conditional_t<bitness == x86, elf_parser::sections32, elf_parser::sections64>>
static inline elf_sections read_section_headers(std::ifstream& file, size_t offset, size_t count)
{
    return read_array<typename elf_sections::value_type>(file, offset, count);
}

template <uint8_t bitness, typename program_headers = std::conditional_t<bitness == x86, elf_parser::headers32, elf_parser::headers64>>
static inline program_headers read_headers(std::ifstream& file, size_t offset, size_t count)
{
    return read_array<typename program_headers::value_type>(file, offset, count);
}

template <uint8_t bitness,
    typename dynamic_sections = std::conditional_t<bitness == x86, elf_parser::dynamic_sections32, elf_parser::dynamic_sections64>>
static dynamic_sections read_dynamic_sections(std::ifstream& file, const size_t offset)
{
    dynamic_sections sections;
    file.seekg(offset);
    while (true)
    {
        typename dynamic_sections::value_type section;
        file.read(reinterpret_cast<char*>(&section), sizeof(sections));

        if (section.d_tag == pt_null)
            break;

        sections.push_back(section);
    }

    return sections;
}

elf_parser::elf_parser(std::filesystem::path path) : path(std::move(path))
{
}

std::optional<file_structure> elf_parser::parse()
{
    file = std::ifstream(path, std::ios::binary);
    if (!file.is_open())
        throw std::runtime_error("can't open file");

    try
    {
        read_ident();
        if (!is_elf())
            return std::nullopt;

        read_header();
        if (!is_exec())
            throw std::runtime_error("is not executable file");

        read_section_headers();
        std::cout << "sections: " << std::get<1>(sections).size() << std::endl;
        read_program_headers();

        if (bitness == x64)
        {
            for (const auto& header : std::get<1>(program_headers))
            {
                switch (header.p_type)
                {
                case pt_dynamic:
                    read_dynamic_sections(header.p_offset);

                    std::cout << std::get<1>(dynamic_sections).size() << std::endl;
                    for (const auto& section : std::get<1>(dynamic_sections))
                    {
                        std::cout << "tag: " << section.d_tag << std::endl;
                        if (section.d_tag != dt_strtab)
                            continue;

                        std::cout << std::hex << section.d_un.d_ptr << std::endl;
                        const auto offset = header.p_offset + section.d_un.d_ptr - header.p_vaddr;
                        std::cout << "offset: " << offset << std::endl;
                        file.seekg(section.d_un.d_ptr);
                        for (size_t i = 0; i < 20; ++i)
                        {
                            char c;
                            file.read(&c, sizeof(c));
                            std::cout << c;
                        }
                        std::cout << std::endl;
                    }

                    break;
                }
            }
        }
    }
    catch (const std::exception& exception)
    {
        std::cout << exception.what() << std::endl;
        return std::nullopt;
    }

    return file_structure{.format = file_structure::executable_format::elf, .bitness = bitness, .import_modules = {}};
}

void elf_parser::read_ident()
{
    file.read(std::launder(reinterpret_cast<char*>(ident.data())), ident.size());

    switch (ident[ei_class])
    {
    case elf_class32:
        bitness = x86;
        break;
    case elf_class64:
        bitness = x64;
        break;
    default:
        throw std::runtime_error("can't get bitness");
    }
}

void elf_parser::read_header()
{
    file.seekg(std::ios_base::beg);

    switch (bitness)
    {
    case x86:
    {
        elf32 header32;
        file.read(std::launder(reinterpret_cast<char*>(&header32)), sizeof(elf32));
        elf_header = header32;
        break;
    }
    case x64:
    {
        elf64 header64;
        file.read(std::launder(reinterpret_cast<char*>(&header64)), sizeof(elf64));
        elf_header = header64;
        break;
    }
    default:
        throw std::runtime_error("unexpected bitness");
    }
}

void elf_parser::read_section_headers()
{
    const size_t offset = bitness == x86 ? std::get<0>(elf_header).e_shoff : std::get<1>(elf_header).e_shoff;
    const auto section_count = bitness == x86 ? std::get<0>(elf_header).e_shnum : std::get<1>(elf_header).e_shnum;
    if (!section_count)
        return;

    sections = APPLY_DEPENDING_ON_BITNESS(analyzer::posix::read_section_headers, bitness)(file, offset, section_count);
}

void elf_parser::read_program_headers()
{
    const size_t offset = bitness == x86 ? std::get<0>(elf_header).e_phoff : std::get<1>(elf_header).e_phoff;
    const size_t header_count = bitness == x86 ? std::get<0>(elf_header).e_phnum : std::get<1>(elf_header).e_phnum;
    if (!header_count)
        return;

    program_headers = APPLY_DEPENDING_ON_BITNESS(analyzer::posix::read_headers, bitness)(file, offset, header_count);
}

bool elf_parser::is_elf() const noexcept
{
    return ident[ei_mag0] == 0x7f &&
           ident[ei_mag1] == 'E' &&
           ident[ei_mag2] == 'L' &&
           ident[ei_mag3] == 'F';
}

bool elf_parser::is_exec() const
{
    uint32_t elf_type{};
    switch (bitness)
    {
    case x86:
        elf_type = std::get<0>(elf_header).e_type;
        break;
    case x64:
        elf_type = std::get<1>(elf_header).e_type;
        break;
    }

    return elf_type == et_exec || elf_type == et_dyn;
}

void elf_parser::read_dynamic_sections(const size_t offset)
{
    dynamic_sections = APPLY_DEPENDING_ON_BITNESS(analyzer::posix::read_dynamic_sections, bitness)(file, offset);
}

} // namespace analyzer::posix
